/*
 *  linux/kernel/printk.c
 *
 *  (C) 1991  Linus Torvalds
 */
// 当处于内核模式时，我们不能使用printf，因为寄存器fs指向其他不感兴趣的地方。自己编制一个printf并在使用前保存fs，一切就解决了
/*
 * When in kernel-mode, we cannot use printf, as fs is liable to
 * point to 'interesting' things. Make a printf with fs-saving, and
 * all is well.
 */
#include <stdarg.h>         //标准参数头文件。以宏的形式定义变量参数列表
#include <stddef.h>         // 标准定义头文件。定义了NULL， offsetof（TYPE, MEMBER）

#include <linux/kernel.h>   // 内核头文件。 含有一些内核常用函数的原形定义

static char buf[1024];
// 下面该函数 vsprintf（）在 linux/kernel/vsprintf.c中92行开始
extern int vsprintf(char * buf, const char * fmt, va_list args);
// 内核使用的显示函数
int printk(const char *fmt, ...)
{
	va_list args;               // va_list 实际上是一个字符指针类型
	int i;

	va_start(args, fmt);        // 参数处理开始函数
	i=vsprintf(buf,fmt,args);   // 使用格式串 fmt 将参数列表 args 输出到buf中 返回值 i 等于输出字符串的长度
	va_end(args);               // 参数处理结束函数
	__asm__("push %%fs\n\t"     // 保存fs
		"push %%ds\n\t"
		"pop %%fs\n\t"          // 令 fs = ds
		"pushl %0\n\t"          // 将字符串长度亚都堆栈（这三个入栈是调用参数）
		"pushl $_buf\n\t"       // 将 buf 的地址压入堆栈。
		"pushl $0\n\t"          // 将数值0压入堆栈。是通道号 channel
		"call _tty_write\n\t"   // 调用 tty_write 函数
		"addl $8,%%esp\n\t"     // 跳过（丢弃）两个入栈参数
		"popl %0\n\t"           // 弹出字符串长度值，作为返回值
		"pop %%fs"              // 恢复原 fs 寄存器
		::"r" (i):"ax","cx","dx");  // 通知编译器，寄存器ax cx dx 值可能已经改变
	return i;                   // 返回字符串长度
}
